﻿using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService;

public partial class Preprocessor
{
    /// <summary>
    ///     计算二元表达式的结果，此方法只计算二元逻辑操作符 - AND OR XOR 三种逻辑。
    /// </summary>
    /// <param name="expr">二元逻辑表达式</param>
    private bool ComputeLogicalBinaryExpression(PreBinaryExpression expr)
    {
        bool left = ComputeExpressionAsBoolean(expr.LeftOperand);
        bool right = ComputeExpressionAsBoolean(expr.RightOperand);

        if (expr.OperatorToken.IsOneOfNode(SyntaxKinds.AndKeywordToken,
                SyntaxKinds.AmpersandAmpersandToken))
        {
            return left && right;
        }

        if (expr.OperatorToken.IsNode(SyntaxKinds.XorKeywordToken))
        {
            return left != right;
        }

        return left || right;
    }


    /// <summary>
    ///     计算一元操作符的结果，如果操作符是符号'-'，此值是负号，只要数字节点的值不是0，
    ///     都返回<see langword="true" />。
    /// </summary>
    /// <param name="expr">一元表达式</param>
    private bool ComputeUnaryExpressionAsBoolean(PreUnaryExpression expr)
    {
        // 负数
        if (expr.OperatorToken.IsNode(SyntaxKinds.MinusToken))
        {
            switch (expr.Expression.Kind)
            {
                case SyntaxKinds.Integer:
                case SyntaxKinds.Double:
                    return expr.Expression.TokenNumberValue != 0;

                default:
                    Raise(DiagnosticDescriptors.InvalidExpression,
                        expr.Expression.Span);
                    return false;
            }
        }

        // 一元操作符只有 ! 和 not，都是'非'逻辑
        return !ComputeExpressionAsBoolean(expr.Expression);
    }


    /// <summary>
    ///     计算表达式的结果并转换为布尔类型值。
    /// </summary>
    /// <param name="expr">预处理器表达式</param>
    /// <returns>如果表达式通过判断，返回<see langword="true" /></returns>
    private bool ComputeExpressionAsBoolean(PreExpression expr)
    {
        switch (expr.Kind)
        {
            case SyntaxKinds.PreDefinedExpression:
                return ComputeDefinedExpression((DefinedExpression)expr);

            case SyntaxKinds.UnaryExpression:
                return ComputeUnaryExpressionAsBoolean((PreUnaryExpression)expr);

            case SyntaxKinds.BinaryExpression:
                return ComputeBinaryExpressionAsBoolean((PreBinaryExpression)expr);

            case SyntaxKinds.String:
                Raise(DiagnosticDescriptors.InvalidExpression, expr.Span);
                return false;

            case SyntaxKinds.Double:
            case SyntaxKinds.Integer:
                return expr.TokenNumberValue != 0;

            case SyntaxKinds.Boolean:
                return expr.TokenBooleanValue;

            case SyntaxKinds.Null:
                return false;
        }

        return false;
    }


    /// <summary>
    ///     计算二元表达式并转换为布尔值返回。
    /// </summary>
    /// <param name="expr">二元表达式</param>
    private bool ComputeBinaryExpressionAsBoolean(PreBinaryExpression expr)
    {
        if (SyntaxFacts.IsComputeOperator(expr.OperatorToken.Kind, OperatorContexts.Preprocessor))
        {
            return TryComputeNumber(expr, out double d) && d != 0;
        }

        if (SyntaxFacts.IsLogicalOperator(expr.OperatorToken.Kind, OperatorContexts.Preprocessor))
        {
            return ComputeLogicalBinaryExpression(expr);
        }

        if (SyntaxFacts.IsCompareOperator(expr.OperatorToken.Kind, OperatorContexts.Preprocessor))
        {
            return ComputeCompareBinaryExpression(expr);
        }

        return false;
    }


    /// <summary>
    ///     计算比较运算符（&gt;，&gt;=，&lt;，&lt;=，=，==）的二元表达式结果。
    /// </summary>
    /// <param name="expr">二元表达式</param>
    private bool ComputeCompareBinaryExpression(PreBinaryExpression expr)
    {
        if (!TryComputeNumber(expr.LeftOperand, out double lVal) ||
            !TryComputeNumber(expr.RightOperand, out double rVal))
        {
            return false;
        }

        switch (expr.OperatorToken.Kind)
        {
            case SyntaxKinds.GreaterToken:
                return lVal > rVal;

            case SyntaxKinds.LessToken:
                return lVal < rVal;

            case SyntaxKinds.GreaterEqualToken:
                return lVal >= rVal;

            case SyntaxKinds.LessEqualToken:
                return lVal <= rVal;

            case SyntaxKinds.EqualToken:
            case SyntaxKinds.EqualEqualToken:
                return lVal == rVal;

            case SyntaxKinds.NotEqualToken:
            case SyntaxKinds.ExclamationEqualToken:
                return lVal != rVal;
        }

        return false;
    }


    /// <summary>
    ///     计算 defined 表达式结果，如果表达式的宏已定义，返回<see langword="true" />
    /// </summary>
    private bool ComputeDefinedExpression(DefinedExpression expr)
    {
        return !SourceNode.IsNullOrDefault(expr.MacroNameNode)
               && _macroSymbolTable.ContainsName(expr.MacroNameNode.Text);
    }


    /// <summary>
    ///     尝试计算表达式的数值结果
    /// </summary>
    /// <param name="expr">预处理表达式</param>
    /// <param name="val">表达式的数值结果</param>
    /// <returns>如果表达式能够计算出数值结果，返回<see langword="true" /></returns>
    private bool TryComputeNumber(PreExpression expr, out double val)
    {
        val = 0;

        if (expr.IsOneOfNode(SyntaxKinds.Integer, SyntaxKinds.Double))
        {
            val = expr.TokenNumberValue;
            return true;
        }

        if (expr.IsNode(SyntaxKinds.Boolean))
        {
            val = expr.TokenBooleanValue ? 1d : 0d;
            return true;
        }

        if (expr.IsNode(SyntaxKinds.Null))
        {
            val = 0;
            return true;
        }

        // unary
        if (expr.IsNode(SyntaxKinds.UnaryExpression))
        {
            PreUnaryExpression unary = (PreUnaryExpression)expr;
            // 负数
            if (unary.OperatorToken.IsNode(SyntaxKinds.MinusToken))
            {
                if (TryComputeNumber(unary.Expression, out double dbVal))
                {
                    val = -dbVal;
                    return true;
                }

                Raise(DiagnosticDescriptors.InvalidExpression, expr.Span);
                return false;
            }

            val = ComputeUnaryExpressionAsBoolean(unary) ? 1d : 0d;
            return true;
        }

        // binary
        if (!expr.IsNode(SyntaxKinds.BinaryExpression))
        {
            return false;
        }

        PreBinaryExpression binary = (PreBinaryExpression)expr;

        if (!TryComputeNumber(binary.LeftOperand, out double lVal) ||
            !TryComputeNumber(binary.RightOperand, out double rVal))
        {
            return false;
        }

        switch (binary.OperatorToken.Kind)
        {
            case SyntaxKinds.PlusToken:
                val = lVal + rVal;
                return true;

            case SyntaxKinds.MinusToken:
                val = lVal - rVal;
                return true;

            case SyntaxKinds.SlashToken:
                if (rVal != 0)
                {
                    val = lVal / rVal;
                    return true;
                }

                Raise(DiagnosticDescriptors.ZeroCannotBeDenominator, expr.Span);
                return false;

            case SyntaxKinds.AsteriskToken:
                val = lVal * rVal;
                return true;

            // logical
            case SyntaxKinds.AndKeywordToken:
            case SyntaxKinds.OrKeywordToken:
            case SyntaxKinds.XorKeywordToken:
            case SyntaxKinds.AmpersandAmpersandToken:
            case SyntaxKinds.BarBarToken:
                val = ComputeLogicalBinaryExpression(binary) ? 1d : 0d;
                return true;

            // compare
            case SyntaxKinds.GreaterToken:
            case SyntaxKinds.GreaterEqualToken:
            case SyntaxKinds.LessToken:
            case SyntaxKinds.LessEqualToken:
            case SyntaxKinds.EqualToken:
            case SyntaxKinds.EqualEqualToken:
            case SyntaxKinds.NotEqualToken:
            case SyntaxKinds.ExclamationEqualToken:
                val = ComputeCompareBinaryExpression(binary) ? 1d : 0d;
                return true;
        }

        return false;
    }
}